﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AvantGameMachine.Graphics
{
    public class Matrix
    {
        private float[,] _values;
        private int _rowCount = 3;
        private int _columnCount = 3;

        public Matrix()
        {
            _values = new float[_rowCount, _columnCount];
        }

        public Matrix(int rowCount, int columnCount)
        {
            _rowCount = rowCount;
            _columnCount = columnCount;
            _values = new float[_rowCount, _columnCount];
        }

        public float this[int row, int column]
        {
            get { return _values[row, column]; }
            set { _values[row, column] = value; }
        }

        public int RowCount
        {
            get { return _rowCount; }
        }

        public int ColumnCount
        {
            get { return _columnCount; }
        }

        public Matrix Clone()
        {
            Matrix resultMatrix = new Matrix(_rowCount, _columnCount);
            for (int i = 0; i < _rowCount; i++)
            {
                for (int j = 0; j < _columnCount; j++)
                {
                    resultMatrix[i, j] = this[i, j];
                }
            }
            return resultMatrix;
        }

        public Matrix Transpose()
        {
            Matrix resultMatrix = new Matrix(_columnCount, _rowCount);
            for (int i = 0; i < _rowCount; i++)
            {
                for (int j = 0; j < _columnCount; j++)
                {
                    resultMatrix[j, i] = this[i, j];
                }
            }
            return resultMatrix;
        }

        public static Matrix Identity(int size)
        {
            Matrix resultMatrix = new Matrix(size, size);
            for (int i = 0; i < size; i++)
            {
                for (int j = 0; j < size; j++)
                {
                    resultMatrix[i, j] = (i == j) ? 1.0f : 0.0f;
                }
            }
            return resultMatrix;
        }

        public static Matrix FromArray(float[] left)
        {
            int length = left.Length;
            Matrix resultMatrix = new Matrix(length, 1);
            for (int i = 0; i < length; i++)
            {
                resultMatrix[i, 0] = left[i];
            }
            return resultMatrix;
        }

        public static implicit operator Matrix(float[] left)
        {
            return FromArray(left);
        }

        public static float[] ToArray(Matrix leftMatrix)
        {
            float[] result = null;
            if (leftMatrix.ColumnCount > 1)
            {
                int numElements = leftMatrix.ColumnCount;
                result = new float[numElements];
                for (int i = 0; i < numElements; i++)
                {
                    result[i] = leftMatrix[0, i];
                }
            }
            else
            {
                int numElements = leftMatrix.RowCount;
                result = new float[numElements];
                for (int i = 0; i < numElements; i++)
                {
                    result[i] = leftMatrix[i, 0];
                }
            }
            return result;
        }

        public static implicit operator float[](Matrix leftMatrix)
        {
            return ToArray(leftMatrix);
        }

        public static Matrix FromFloatArray(float[,] left)
        {
            int length0 = left.GetLength(0);
            int length1 = left.GetLength(1);
            Matrix resultMatrix = new Matrix(length0, length1);
            for (int i = 0; i < length0; i++)
            {
                for (int j = 0; j < length1; j++)
                {
                    resultMatrix[i, j] = left[i, j];
                }
            }
            return resultMatrix;
        }

        public static implicit operator Matrix(float[,] left)
        {
            return FromFloatArray(left);
        }

        public static float[,] ToFloatArray(Matrix leftMatrix)
        {
            float[,] result = new float[leftMatrix.RowCount, leftMatrix.ColumnCount];
            for (int i = 0; i < leftMatrix.RowCount; i++)
            {
                for (int j = 0; j < leftMatrix.ColumnCount; j++)
                {
                    result[i, j] = leftMatrix[i, j];
                }
            }
            return result;
        }

        public static implicit operator float[,](Matrix leftMatrix)
        {
            return ToFloatArray(leftMatrix);
        }

        public static Matrix Add(Matrix leftMatrix, Matrix rightMatrix)
        {
            Matrix resultMatrix = new Matrix(leftMatrix.RowCount, rightMatrix.ColumnCount);
            for (int i = 0; i < leftMatrix.RowCount; i++)
            {
                for (int j = 0; j < leftMatrix.ColumnCount; j++)
                {
                    resultMatrix[i, j] = leftMatrix[i, j] + rightMatrix[i, j];
                }
            }
            return resultMatrix;
        }

        public static Matrix operator +(Matrix leftMatrix, Matrix rightMatrix)
        {
            return Matrix.Add(leftMatrix, rightMatrix);
        }

        public static Matrix Subtract(Matrix leftMatrix, Matrix rightMatrix)
        {
            Matrix resultMatrix = new Matrix(leftMatrix.RowCount, rightMatrix.ColumnCount);
            for (int i = 0; i < leftMatrix.RowCount; i++)
            {
                for (int j = 0; j < leftMatrix.ColumnCount; j++)
                {
                    resultMatrix[i, j] = leftMatrix[i, j] - rightMatrix[i, j];
                }
            }
            return resultMatrix;
        }

        public static Matrix operator -(Matrix leftMatrix, Matrix rightMatrix)
        {
            return Matrix.Subtract(leftMatrix, rightMatrix);
        }

        public static Matrix Multiply(Matrix leftMatrix, Matrix rightMatrix)
        {
            Matrix resultMatrix = new Matrix(leftMatrix.RowCount, rightMatrix.ColumnCount);
            for (int i = 0; i < resultMatrix.ColumnCount; i++)
            {
                for (int j = 0; j < leftMatrix.RowCount; j++)
                {
                    float value = 0.0f;
                    for (int k = 0; k < rightMatrix.RowCount; k++)
                    {
                        value += leftMatrix[j, k] * rightMatrix[k, i];
                    }
                    resultMatrix[j, i] = value;
                }
            }
            return resultMatrix;
        }

        public static Matrix operator *(Matrix leftMatrix, Matrix rightMatrix)
        {
            return Matrix.Multiply(leftMatrix, rightMatrix);
        }

        public static Matrix Multiply(float left, Matrix rightMatrix)
        {
            Matrix resultMatrix = new Matrix(rightMatrix.RowCount, rightMatrix.ColumnCount);
            for (int i = 0; i < resultMatrix.RowCount; i++)
            {
                for (int j = 0; j < rightMatrix.ColumnCount; j++)
                {
                    resultMatrix[i, j] = left * rightMatrix[i, j];
                }
            }
            return resultMatrix;
        }

        public static Matrix operator *(float left, Matrix rightMatrix)
        {
            return Matrix.Multiply(left, rightMatrix);
        }

        public static Matrix GetRotationMatrixX(float _angle)
        {
            float sin = (float)Math.Sin(_angle);
            float cos = (float)Math.Cos(_angle);

            Matrix m = Matrix.FromFloatArray(new float[,]
                {
                    {1.0f,      0.0f,       0.0f,       0.0f},
                    {0.0f,      cos,        -1 * sin,   0.0f},
                    {0.0f,      sin,        cos,        0.0f},
                    {0.0f,      0.0f,       0.0f,       1.0f},
                }
            );
            return m;
        }

        public static Matrix GetRotationMatrixY(float _angle)
        {
            float sin = (float)Math.Sin(_angle);
            float cos = (float)Math.Cos(_angle);

            Matrix m = Matrix.FromFloatArray(new float[,]
                {
                    {cos,       0.0f,       sin,        0.0f},
                    {0.0f,      1.0f,       0.0f,       0.0f},
                    {-1 * sin,  0.0f,       cos,        0.0f},
                    {0.0f,      0.0f,       0.0f,       1.0f},
                }
            );
            return m;
        }

        public static Matrix GetRotationMatrixZ(float _angle)
        {
            float sin = (float)Math.Sin(_angle);
            float cos = (float)Math.Cos(_angle);

            Matrix m = Matrix.FromFloatArray(new float[,]
                {
                    {cos,       -1 * sin,   0.0f,       0.0f},
                    {sin,       cos,        0.0f,       0.0f},
                    {0.0f,      0.0f,       1.0f,       0.0f},
                    {0.0f,      0.0f,       0.0f,       1.0f},
                }
            );
            return m;
        }

        public static Matrix operator *(Matrix leftMatrix,PosVector _vector)
        {
            Matrix m = Matrix.FromArray(new float[] { _vector.X,_vector.Y,_vector.Z,0.0f});
            return leftMatrix * m;
        }

        public static Matrix operator *(PosVector _vector, Matrix _rightMatrix)
        {
            Matrix m = Matrix.FromArray(new float[] { _vector.X, _vector.Y, _vector.Z, 0.0f });
            return m * _rightMatrix;
        }

        public PosVector GetVectorLine(int _line, int _firstCol)
        {
            return new PosVector(_values[_line, _firstCol], _values[_line, _firstCol + 1], _values[_line, _firstCol + 2]);
        }

        public PosVector GetVectorColumn(int _firstLine, int _col)
        {
            return new PosVector(_values[_firstLine, _col], _values[_firstLine + 1, _col], _values[_firstLine + 2, _col]);
        }

        public PosVector GetDiagonalDownRight(int _firstLine, int _firstCol)
        {
            return new PosVector(_values[_firstLine, _firstCol], _values[_firstLine + 1, _firstCol + 1], _values[_firstLine + 2, _firstCol + 2]);
        }

        public override string ToString()
        {
            string temp = string.Empty;
            string total = string.Empty;

            for (int y = 0; y < RowCount; y++)
            {
                if (y > 0)
                {
                    total += "\t";
                }
                temp = "[";
                for (int x = 0; x < ColumnCount; x++)
                {
                    if (x > 0)
                        temp += "|";

                    temp += _values[y, x].ToString();
                }

                temp += "]";
                total += temp;
            }
            return total;
        }
    }
}
